package de.westranger.forex.trading.genetic.allel.crossover;

import java.util.List;
import java.util.Random;

import de.westranger.forex.trading.GlobalConfig;
import de.westranger.forex.trading.genetic.allel.Node;
import de.westranger.forex.trading.genetic.allel.chromosom.Chromosom;
import de.westranger.forex.trading.genetic.allel.chromosom.CompositeChromosom;
import de.westranger.forex.trading.genetic.allel.chromosom.SimpleChromosom;
import de.westranger.forex.trading.genetic.allel.operator.EvaluateTerminal;
import de.westranger.forex.trading.genetic.allel.operator.Terminal;
import de.westranger.forex.trading.genetic.allel.operator.Util;

public final class Crossover {
	private final Random rnd;
	private List<Node> list;
	
	public Crossover(final Random rnd){
		this.rnd = rnd;
	}

	public Chromosom[] mate(final Chromosom chromA, final Chromosom chromB){
		Chromosom[] result = null;
		if(chromA instanceof CompositeChromosom && chromB instanceof CompositeChromosom){
			Node[] nodeSell = this.mateNode(((CompositeChromosom)chromA).getNodeSell(), ((CompositeChromosom)chromB).getNodeSell());
			if(nodeSell == null){
				 nodeSell = this.mateNode(((CompositeChromosom)chromA).getNodeSell(), ((CompositeChromosom)chromB).getNodeSell());
			}

			Node[] nodeBuy = this.mateNode(((CompositeChromosom)chromA).getNodeBuy(), ((CompositeChromosom)chromB).getNodeBuy());
			if(nodeBuy == null){
				nodeBuy = this.mateNode(((CompositeChromosom)chromA).getNodeBuy(), ((CompositeChromosom)chromB).getNodeBuy());
			}

			if(nodeSell != null && nodeBuy != null){
				result = new Chromosom[]{new CompositeChromosom(0.0,nodeSell[0],nodeBuy[0]),new CompositeChromosom(0.0,nodeSell[1],nodeBuy[0])};				
			}
			
			if(result != null){
				if(rnd.nextBoolean()){ // excahnge take profit
					((CompositeChromosom)result[0]).setTakeProfit(((CompositeChromosom)chromB).getTakeProfit());					
					((CompositeChromosom)result[1]).setTakeProfit(((CompositeChromosom)chromB).getTakeProfit());					
				}else{
					((CompositeChromosom)result[0]).setTakeProfit(((CompositeChromosom)chromA).getTakeProfit());	
					((CompositeChromosom)result[1]).setTakeProfit(((CompositeChromosom)chromB).getTakeProfit());
				}
				if(rnd.nextBoolean()){ // excahnge stoploss
					((CompositeChromosom)result[0]).setStoploss(((CompositeChromosom)chromB).getStoploss());
					((CompositeChromosom)result[1]).setStoploss(((CompositeChromosom)chromA).getStoploss());
				}else{
					((CompositeChromosom)result[0]).setStoploss(((CompositeChromosom)chromA).getStoploss());
					((CompositeChromosom)result[1]).setStoploss(((CompositeChromosom)chromB).getStoploss());
				}
				
				if(rnd.nextBoolean()){// exchange start hour
					((CompositeChromosom)result[0]).setTradingStartHour(((CompositeChromosom)chromB).getTradingStartHour());
					((CompositeChromosom)result[1]).setTradingStartHour(((CompositeChromosom)chromA).getTradingStartHour());
				}else{
					((CompositeChromosom)result[0]).setTradingStartHour(((CompositeChromosom)chromA).getTradingStartHour());
					((CompositeChromosom)result[1]).setTradingStartHour(((CompositeChromosom)chromB).getTradingStartHour());	
				}

				if(rnd.nextBoolean()){// exchange end hour 
					((CompositeChromosom)result[0]).setTradingEndHour(((CompositeChromosom)chromB).getTradingEndHour());
					((CompositeChromosom)result[1]).setTradingEndHour(((CompositeChromosom)chromA).getTradingEndHour());
				}else{
					((CompositeChromosom)result[0]).setTradingEndHour(((CompositeChromosom)chromA).getTradingEndHour());
					((CompositeChromosom)result[1]).setTradingEndHour(((CompositeChromosom)chromB).getTradingEndHour());	
				}
			}

		}else if(chromA instanceof SimpleChromosom && chromB instanceof SimpleChromosom){
			final Node[] node = this.mateNode(((SimpleChromosom)chromA).getNode(), ((SimpleChromosom)chromB).getNode());
			if(node != null){
				result = new Chromosom[]{new SimpleChromosom(0.0,node[0]),new SimpleChromosom(0.0,node[1])};				
			}
		}else{
			throw new IllegalArgumentException("Chromosom class is "+chromA.getClass().getSimpleName()+" and "+chromB.getClass().getSimpleName() + " are unknown or can't be combined");
		}
		return result;
	}

	private Node[] mateNode(final Node indA, final Node indB){
		Node individuumA = Util.cloneExpression(indA,null);
		Node individuumB = Util.cloneExpression(indB,null);
		Node selectedA = queryAndSelectRnd(individuumA);
		Node selectedB = queryAndSelectRnd(individuumB);
		
		if((selectedA instanceof Terminal && !(selectedB instanceof Terminal)) || (!(selectedA instanceof Terminal) && selectedB instanceof Terminal)){
			selectedA = queryAndSelectRnd(individuumA);
			selectedB = queryAndSelectRnd(individuumB);
			
			if((selectedA instanceof Terminal && !(selectedB instanceof Terminal)) || (!(selectedA instanceof Terminal) && selectedB instanceof Terminal)){
				selectedA = null;
				selectedB = null;
			}
		}
		
		Node[] result = null;
		if(selectedA != null){ // A and B do always have the same "state" because there are always operator and always terminals, if the wouldn't then one could eventually be null and the other not
			if(!selectedA.equals(individuumA) && !selectedB.equals(individuumB)){
				Util.exchange(selectedA, selectedB);
				result = new Node[]{individuumA,individuumB};
			}else if(selectedA.equals(individuumA) && selectedB.equals(individuumB)){
				individuumA = selectedB;
				individuumA.setRoot(null);
				individuumB = selectedA;
				individuumB.setRoot(null);
				result = new Node[]{individuumA,individuumB};
			}else if(selectedA.equals(individuumA) && !(selectedB instanceof EvaluateTerminal)){
				Util.exchange(selectedA, selectedB);
				individuumA = selectedB;
				individuumA.setRoot(null);
				result = new Node[]{individuumA,individuumB};
			}else if(selectedB.equals(individuumB) && !(selectedA instanceof EvaluateTerminal)){
				Util.exchange(selectedA, selectedB);
				individuumB = selectedA;
				individuumB.setRoot(null);
				result = new Node[]{individuumA,individuumB};
			}
		}

		return result;
	}

	private Node queryAndSelectRnd(final Node node){
		
		if(this.rnd.nextDouble() < GlobalConfig.OPERATOR_SELECTION_PROBABILITY){
			this.list = Util.getQueryNodes(node, Util.QUERY_OPERATORS);
		}else{
			this.list = Util.getQueryNodes(node, Util.QUERY_TERMINALS);
		}

		return this.list.get(this.rnd.nextInt(this.list.size()));
	}
}
